package controller

import (
	"dgg/pkg/response"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/gin-gonic/gin"
)

// RegisterRoutes registers all file system related routes
// SaveFileRequest represents the request body for saving file content
type SaveFileRequest struct {
	Path    string `json:"path" binding:"required"`
	Content string `json:"content" binding:"required"`
}

func (fs *FSController) RegisterRoutes(group *gin.RouterGroup) {
	group.GET("/list", fs.ListFiles)
	group.GET("/get", fs.GetFileContent)
	group.POST("/save", fs.SaveFileContent)
}

// SaveFileContent handles saving content to a file
func (fs *FSController) SaveFileContent(c *gin.Context) {
    var req SaveFileRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        response.BadRequest(c, "Invalid request body: "+err.Error())
        return
    }

    // Prevent directory traversal attacks
    if strings.Contains(req.Path, "..") {
        response.BadRequest(c, "Invalid file path")
        return
    }

    // 使用传入的路径作为绝对路径
    absPath := req.Path

    // Ensure the directory exists
    dir := filepath.Dir(absPath)
    if err := os.MkdirAll(dir, 0755); err != nil {
        response.InternalError(c, "Failed to create directory: "+err.Error())
        return
    }

    // Write content to file
    if err := os.WriteFile(absPath, []byte(req.Content), 0644); err != nil {
        response.InternalError(c, "Failed to write file: "+err.Error())
        return
    }

    response.Success(c, gin.H{
        "message": "File saved successfully",
        "path":    req.Path,
    })
}

// FSController handles file system related endpoints
type FSController struct{}

// NewFSController creates a new instance of FSController
func NewFSController() *FSController {
	return &FSController{}
}

// FileInfo represents information about a file or directory
type FileInfo struct {
	Name     string     `json:"name"`
	IsFolder bool       `json:"isFolder"`
	Path     string     `json:"path"`
	Children []FileInfo `json:"children,omitempty"`
}

// listFiles recursively lists all files and directories
func (fs *FSController) listFiles(root string) (*FileInfo, error) {
	info, err := os.Stat(root)
	if err != nil {
		return nil, err
	}

	baseName := filepath.Base(root)
	absPath, err := filepath.Abs(root)
	if err != nil {
		return nil, err
	}

	result := &FileInfo{
		Name:     baseName,
		Path:     absPath,
		IsFolder: true,
	}

	if !info.IsDir() {
		// Skip binary and non-editable files
		ext := strings.ToLower(filepath.Ext(baseName))
		binaryExts := map[string]bool{
			".exe": true, ".dll": true, ".so": true, ".dylib": true,
			".bin": true, ".obj": true, ".o": true, ".a": true,
			".lib": true, ".pyc": true, ".pyo": true, ".pyd": true,
			".class": true, ".jar": true, ".war": true, ".ear": true,
			".png": true, ".jpg": true, ".jpeg": true, ".gif": true,
			".bmp": true, ".ico": true, ".webp": true, ".tiff": true,
			".mp3": true, ".mp4": true, ".avi": true, ".mov": true,
			".wmv": true, ".flv": true, ".mkv": true, ".wav": true,
			".zip": true, ".rar": true, ".7z": true, ".tar": true,
			".gz": true, ".bz2": true, ".xz": true, ".iso": true,
		}

		if binaryExts[ext] {
			return nil, nil
		}

		result.IsFolder = false
		result.Path = absPath
		return result, nil
	}

	entries, err := os.ReadDir(root)
	if err != nil {
		return nil, err
	}

	for _, entry := range entries {
		childPath := filepath.Join(root, entry.Name())
		child, err := fs.listFiles(childPath)
		if err != nil {
			return nil, err
		}
		if child != nil {
			result.Children = append(result.Children, *child)
		}
	}

	return result, nil
}

// ListFiles handles the list files endpoint
func (fs *FSController) ListFiles(c *gin.Context) {
	// Get current working directory
	cwd, err := os.Getwd()
	if err != nil {
		response.InternalError(c, "Failed to get working directory: "+err.Error())
		return
	}

	// Get directory path from query parameter
	dirPath := c.Query("path")
	if dirPath == "" {
		dirPath = "."
	}

	// Prevent directory traversal attacks
	if strings.Contains(dirPath, "..") {
		response.BadRequest(c, "Invalid directory path")
		return
	}

	// Construct absolute directory path
	absPath := filepath.Join(cwd, dirPath)

	// Get file tree structure
	fileTree, err := fs.listFiles(absPath)
	if err != nil {
		response.InternalError(c, "Failed to get file tree: "+err.Error())
		return
	}

	// Create root node
	root := FileInfo{
		Name:     "我的电脑",
		IsFolder: true,
		Children: []FileInfo{*fileTree},
	}

	response.Success(c, root)
}

// GetFileContent handles the get file content endpoint
func (fs *FSController) GetFileContent(c *gin.Context) {
	// Get file path from query parameter
	filePath := c.Query("path")
	if filePath == "" {
		response.BadRequest(c, "File path is required")
		return
	}

	// Prevent directory traversal attacks
	if strings.Contains(filePath, "..") {
		response.BadRequest(c, "Invalid file path")
		return
	}

	// Get current working directory

	// Construct absolute file path
	// absPath := filepath.Join(cwd, filePath)

	// Check if file exists and is not a directory
	fileInfo, err := os.Stat(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			response.BadRequest(c, "File not found")
			return
		}
		response.InternalError(c, "Failed to access file: "+err.Error())
		return
	}

	if fileInfo.IsDir() {
		response.BadRequest(c, "Path points to a directory, not a file")
		return
	}

	// Check if file is binary
	ext := strings.ToLower(filepath.Ext(filePath))
	binaryExts := map[string]bool{
		".exe": true, ".dll": true, ".so": true, ".dylib": true,
		".bin": true, ".obj": true, ".o": true, ".a": true,
		".lib": true, ".pyc": true, ".pyo": true, ".pyd": true,
		".class": true, ".jar": true, ".war": true, ".ear": true,
		".png": true, ".jpg": true, ".jpeg": true, ".gif": true,
		".bmp": true, ".ico": true, ".webp": true, ".tiff": true,
		".mp3": true, ".mp4": true, ".avi": true, ".mov": true,
		".wmv": true, ".flv": true, ".mkv": true, ".wav": true,
		".zip": true, ".rar": true, ".7z": true, ".tar": true,
		".gz": true, ".bz2": true, ".xz": true, ".iso": true,
	}

	if binaryExts[ext] {
		response.BadRequest(c, "二进制文件无法读取")
		return
	}

	// Read file content
	content, err := ioutil.ReadFile(filePath)
	if err != nil {
		response.InternalError(c, "Failed to read file: "+err.Error())
		return
	}

	response.Success(c, gin.H{
		"content":    string(content),
		"size":       fileInfo.Size(),
		"abs_path":   filePath,
		"created_at": fileInfo.ModTime(),
	})
}
